//! Common IR block and statement types.
//!
//! These make up the coarse structure of control flow within a function.

use stencil_tysys::{FieldRef, IntEncoding, LocalRef, ValueType};

use super::TypedExpr;
use crate::AbortReason;

/// A block is MIR statements, executed in sequential order.
#[derive(Clone, Debug)]
pub struct MirBlock {
    /// The list of statements in the block.
    statements: Vec<MirStatement>,
}

impl MirBlock {
    pub fn new(statements: Vec<MirStatement>) -> Self {
        Self { statements }
    }

    pub fn statements(&self) -> &[MirStatement] {
        &self.statements
    }
}

/// A statement within a MIR block, performing some operation, possibly
/// containing other blocks which might or might not be executed.
#[derive(Clone, Debug)]
pub enum MirStatement {
    /// Comment for debugging.  Has no effect.
    Comment(String),

    /// Declare a local without assigning it, to indicate that it's used in this
    /// outer scope.  This should then be unconditionally filled in by a
    /// subsequent expression.
    DeclareHint(LocalRef, ValueType),

    /// Assign an expression's value to a local variable.  In a particular
    /// language it may be necessary to flatten the expression out in a couple
    /// of places in order to properly express it.
    ///
    /// (local, value)
    Assign(AssignTarget, TypedExpr),

    /// Assign an expression's value to an index in a local.
    ///
    /// (local, index, value)
    AssignIndex(AssignTarget, TypedExpr, TypedExpr),

    /// Assign bytes in a buffer at some offset an expression's value.
    ///
    /// (local, offset, value, enc)
    AssignBufInt(AssignTarget, TypedExpr, TypedExpr, IntEncoding),

    /// Return from the function with no value.
    ///
    /// This might be compiled the same as returning a "null" value depending on
    /// the language.
    ReturnVoid,

    /// Return from the function with some expression's value.
    ReturnValue(TypedExpr),

    /// If some expression is true, execute a block.
    If(TypedExpr, Box<MirBlock>),

    /// If some expression is true, execute one block, otherwise execute the
    /// other.
    IfElse(TypedExpr, Box<MirBlock>, Box<MirBlock>),

    /// Aborts the function with some reason.
    ///
    /// This might be implemented as an exception or as some erroneous result,
    /// depending on the language's conventions.
    Abort(AbortReason),

    /// Constructs the object instance.
    ///
    /// This only makes sense in a constructor, and only as the last statement
    /// of the constructor's block.  This is necessary since languages have more
    /// varied ways to construct instances of objects.
    Construct(CtorAssignments),
}

/// A target we can assign to, like an lvalue.
#[derive(Copy, Clone, Debug)]
pub enum AssignTarget {
    /// Local variable.
    Local(LocalRef),

    /// Field on the self object, if an instance function.
    InstanceField(FieldRef),
}

#[derive(Clone, Debug)]
pub struct CtorAssignments {
    assignments: Vec<CtorAssign>,
}

impl CtorAssignments {
    pub fn new() -> Self {
        Self {
            assignments: Vec::new(),
        }
    }

    pub fn assignments(&self) -> &[CtorAssign] {
        &self.assignments
    }

    /// Finds the value of an assignment.
    pub fn find_assignment(&self, f: FieldRef) -> Option<&TypedExpr> {
        self.assignments
            .iter()
            .filter(|a| a.field == f)
            .map(|a| &a.expr)
            .next()
    }

    /// Adds an assignment if the field is not already defined.
    ///
    /// Returns if it was actually added.
    pub fn add_assignment(&mut self, f: FieldRef, e: TypedExpr) -> bool {
        if self.find_assignment(f).is_some() {
            return false;
        }
        self.assignments.push(CtorAssign::new(f, e));
        true
    }
}

/// An assignment to a field in a constructor.
#[derive(Clone, Debug)]
pub struct CtorAssign {
    field: FieldRef,
    expr: TypedExpr,
}

impl CtorAssign {
    pub fn new(field: FieldRef, expr: TypedExpr) -> Self {
        Self { field, expr }
    }

    pub fn field(&self) -> FieldRef {
        self.field
    }

    pub fn expr(&self) -> &TypedExpr {
        &self.expr
    }
}
